/**
 * Project 68th
 */


#include "../include/TaskQueue.h"
#include "../include/log4cppuse.h"

/**
 * TaskQueue implementation
 */


/**
 * @param cap
 */
TaskQueue::TaskQueue(size_t cap) 
: _capacity(cap)
, _flag(true)
{

}

TaskQueue::~TaskQueue() {

}

/**
 * @param val
 * @return void
 */
//生产者线程执行push
void TaskQueue::push(ElemType && val) {
    //1.先上锁
    /* _mtx.lock(); */
    unique_lock<mutex> ul(_mtx);

    //2.判断仓库是否为满
    /* if(full()) */
    while(full()) //防止虚假唤醒
    {
        //如果仓库是满的，应该让生产者线程等待
        //condition_variable的wait函数必须接收unique_lock
        //
        //底层原理：让生产者线程先解锁
        //等到被唤醒时再重新持有锁
        _notFull.wait(ul);
    }

    //如果仓库不为满，那么就将数据添加到仓库中
    _que.push(val);

    //唤醒消费者
    _notEmpty.notify_one();

    //3.解锁
}

/**
 * @return int
 */
//消费者线程执行pop函数
ElemType TaskQueue::pop() {
    unique_lock<mutex> ul(_mtx);

    //原本pop函数执行过程中，消费者线程发现
    //任务队列为空的时候，就wait
    //不为空的时候，就获取任务
    //
    //现在，情况发生了变化，
    //如果线程池没有要退出，还是之前的逻辑
    //如果线程池要退出，虽然任务队列为空，也不应该wait
    //
    //解决方式：子线程是否wait的判断中添加一个标志位
    //一开始_flag初始化为true，不影响原有的逻辑
    //但是线程池退出时，调用wakeup函数
    //在其中先将_flag置为false，再唤醒所有的子线程
    //子线程再进行此处的判断是，一定是false
    //就不会再wait
    while(empty() && _flag)
    {
        _notEmpty.wait(ul);
    }
    
    //如果线程池没有要退出，那么确实可以
    //从任务队列中取出任务
    //如果线程池要退出，可能任务队列已经为空
    //但是由于_flag参与判断，还是会走到这里
    //实际上是无法获取到任务
    //对一个空的std::queue执行pop可能发生未定义行为
    if(_flag)
    {
        ElemType temp = _que.front();
        _que.pop();

        //唤醒生产者
        _notFull.notify_one();

        return temp;
    }
    else
    {
        return function<void()>();
    }

}

/**
 * @return bool
 */
bool TaskQueue::full() {
    return _que.size() == _capacity;
}

/**
 * @return bool
 */
bool TaskQueue::empty() {
    return _que.empty();
}

//线程池要退出时
//才会执行wakeup
void TaskQueue::wakeup()
{
    _flag = false;
    _notEmpty.notify_all();
}
